/**
 * 2022/11/17 by lin
 * 拉霸专用滚动容器
 */
import { _decorator, Component, Node, Prefab, instantiate, UITransform, Vec3, tween } from 'cc';
const { ccclass, property } = _decorator;

export enum NBSlotsScrollState {
    /**
     * 静止
     */
    Motionless,
    /**
     * 匀速运动
     */
    Constant,
    /**
     * 快速运动
     */
    Fast,
    /**
     * 回弹
     */
    Springback,
}


export interface NBSlotsScrollIF{
    /**
     * 滚动速度 (每帧滚动距离单位像素)
     * default 30px/fps
     */
    constantSpeed: number
    /**
     * 滚动持续总时间 单位秒
     * default 0.5s
     */
    duration: number
    /**
     * 回弹距离 单位像素
     * default 50px
     */
    inertanceDistance: number
    /**
     * 回弹时间 单位秒
     * default 0.3s
     */
    inertanceDuration: number
    /**
     * 回弹开关
     * default true
     */
    inertanceSwitch: boolean
    /**
     * 快速滚动开关
     */
    fastRollSwitch: boolean
    /**
     * 快速滚动速度
     */
    fastRollSpeed: number
    /**
     * 快速滚动持续时长
     */
    fastRollDuration: number
    /**
     * 快速滚动触发时间点
     */
    fastRollTriggerPoint: number
    /**
     * 启动滚动
     */
    startScroll();
    /**
     * 获取停止下标
     */
    calculateStopIndex();
    /**
     * 重置数据
     */
    reset();
    /**
     * 运行状态
     */
    runState(): NBSlotsScrollState;

    /**
     * 滚动钩子
     */
    rollHook:{
        /**
         * 刷新节点
         */
        refreshItem?: (node: Node, index: number) => void;
        /**
         * 滚动开始
         */
        rollStart?:Function
        /**
         * 快速滚动开始
        */
        fastRollStart?:Function,
        /**
         * 回弹开始
         */
        springbackStart?:Function,
        /**
         * 滚动结束
         */
        rollEnd?:Function,
        /**
         * 是否继续滚动
         */
        continueRoll?:() => boolean;
    }
}

@ccclass('NBSlotsScroll')
export default class NBSlotsScroll extends Component implements NBSlotsScrollIF {


    //节点预制体
    @property({ type: Prefab })
    templateNode: Prefab = null;
    @property(Node)
    contentNode: Node = null;
    //几行
    @property({ displayName: '一列多少行' })
    rows: number = 0;
    @property({ displayName: '滚动速度(每帧滚动距离单位像素)' })
    constantSpeed = 30;
    @property({ displayName: '滚动持续总时间 单位秒' })
    duration: number = 0.5;


    @property({ displayName: '回弹开关' })
    inertanceSwitch: boolean = true;
    @property({
        displayName: '回弹距离 单位像素', visible() {
            return this.inertanceSwitch
        }
    })
    inertanceDistance: number = 50;
    @property({
        displayName: '回弹时间 单位秒', visible() {
            return this.inertanceSwitch
        }
    })
    inertanceDuration: number = 0.3;

    @property({ displayName: '快速滚动开关' })
    fastRollSwitch: boolean = false;
    @property({
        displayName: '快速滚动速度（每帧滚动距离单位像素）', visible() {
            return this.fastRollSwitch
        }
    })
    fastRollSpeed: number = 60;
    @property({
        displayName: '快速滚动速度 持续时间 单位秒', visible() {
            return this.fastRollSwitch
        }
    })
    fastRollDuration: number = 0.5;
    @property({
        displayName: '快速滚动触发时间点 单位秒', visible() {
            return this.fastRollSwitch
        }
    })
    fastRollTriggerPoint: number = 0.45;

    rollHook: {
        refreshItem?: (node: Node, index: number) => void;
        rollStart?: Function;
        fastRollStart?: Function;
        springbackStart?: Function;
        rollEnd?: Function;
        continueRoll?: () => boolean;
    } = {}

    //真实滚动速度
    private realySpeed = 0;
    //真实fast滚动速度
    private realyFastRollSpeed = 0;
    //节点高度
    private itemHeight: number = 0;
    //显示节点
    private childrens: Node[] = [];
    //节点池
    private nodePool: Node[] = [];
    //运行状态
    private runFlag: NBSlotsScrollState = NBSlotsScrollState.Motionless;
    //运行过的时间
    private elapsed: number = 0;
    //滚动到目标的距离
    private targetContentPosY = 0;
    //c容器上一次滚动位置
    private lastContentPosY = 0;
    //总节点个数
    private numItems = Number.MAX_SAFE_INTEGER;


    //一个周期间隔距离
    private deltaTargetDistance = 0; 
    //一个周期间隔下标
    private deltaTargetIndex = 0;
    //周期
    private cycle = 0;

    onLoad() {
        const node: Node = instantiate(this.templateNode);
        this.itemHeight = node.getComponent(UITransform).height;
        this.contentNode.getComponent(UITransform).height = Number.MAX_SAFE_INTEGER;
        this.nodePool.push(node);

        const height = this.itemHeight * this.rows;
        this.node.getComponent(UITransform).height = height;
    }

    start(){
        this._startCreateItems();
    }

    /**
     * 刷新
     */
    refresh(){
        this.childrens.forEach((v, index) => {
            this.rollHook.refreshItem?.(v,index);
        })
    }

    //创建节点
    private _startCreateItems() {
        const count = this.rows + 1;
        for (let i = 0; i < count; ++i) {
            const node = this._getItem();
            if (i == 0) {
                node['itemIndex'] = this.numItems - 1;
            } else {
                node['itemIndex'] = i - 1;
            }
            this.contentNode.addChild(node);
            this.childrens.push(node);
        }

        //处理所有子节点位置
        this.childrens.forEach((v, index) => {
            const pos = new Vec3(v.position);
            pos.y = this.itemHeight / 2 + (index - 1) * this.itemHeight;
            v.setPosition(pos)
        })

        this.childrens.forEach((v, index) => {
            this.rollHook.refreshItem?.(v,index);
        })
    }

    /**获取一个列表项 优先从内存池获取*/
    private _getItem(): Node {
        let item: Node = this.nodePool.pop()
        if (!item) {
            item = instantiate(this.templateNode)
        }
        return item;
    }
    //将节点放入内存池并删除
    private _removeItem(item: Node) {
        if (!item) {
            return;
        }
        item.removeFromParent();
        this.nodePool.push(item)
    }


    //节点在窗口的位置
    private _getPositionInView(item: Node): Vec3 {
        let worldPos = this.contentNode.getComponent(UITransform).convertToWorldSpaceAR(item.position);
        let viewPos = this.node.getComponent(UITransform).convertToNodeSpaceAR(worldPos);
        return viewPos;
    }

    //创建下一个
    private _createNextItem() {
        const firstItem = this.childrens[0];
        if (firstItem) {
            const index = firstItem['itemIndex'] - 1;
            if (index < 0) {
                return;
            }
            const item: Node = this._getItem();
            item['itemIndex'] = index;
            item.position = new Vec3(firstItem.position.x, firstItem.position.y - this.itemHeight, firstItem.position.z)
            if (this._inScreenByItem(item)) {
                this.contentNode.addChild(item);
                this.childrens.unshift(item);
                this.rollHook.refreshItem?.(item,index);
                this._createNextItem();
            }
        } else {
            console.log('异常')
        }
    }

    //创建上一个
    private _createPreviousItem() {
        const lastItem = this.childrens[this.childrens.length - 1];
        if (lastItem) {
            const index = lastItem['itemIndex'] + 1;
            if (index >= this.numItems) {
                return;
            }
            const item: Node = this._getItem();
            item['itemIndex'] = index;
            item.position = new Vec3(lastItem.position.x, lastItem.position.y + this.itemHeight, lastItem.position.z)
            if (this._inScreenByItem(item)) {
                this.contentNode.addChild(item);
                this.childrens.push(item);
                this.rollHook.refreshItem?.(item,index);
                this._createPreviousItem();
            } else {
                this.nodePool.push(item);
            }
        } else {
            console.log('异常')
        }
    }

    //节点是否在窗口内
    private _inScreenByItem(item: Node) {
        if (!item) {
            return;
        }
        let index = item['itemIndex'];
        if (index < 0 || index >= this.numItems) {
            return false;
        }
        const point = this._getPositionInView(item);
        const halfScrollView = this.node.getComponent(UITransform).height / 2;
        if (
            point.y - this.itemHeight / 2 > halfScrollView ||
            point.y + this.itemHeight / 2 < -halfScrollView
        ) {
            return false;
        }
        return true
    }

    


    update(dt) {
        if (this.runFlag === NBSlotsScrollState.Motionless) {
            return
        }
        this.step(dt);
    }

    private step(dt: number) {
        this.elapsed += dt;
        if (this.runFlag === NBSlotsScrollState.Constant) {
            const duration = this.fastRollSwitch ? this.fastRollTriggerPoint : this.duration;
            let t = this.elapsed / (duration > 0.0000001192092896 ? duration : 0.0000001192092896);
            t = (t < 1 ? t : 1);
            this.constantUpdate(t);
        }else if(this.runFlag === NBSlotsScrollState.Fast){
            let t = this.elapsed / (this.fastRollDuration > 0.0000001192092896 ? this.fastRollDuration : 0.0000001192092896);
            t = (t < 1 ? t : 1);
            this.fastRollUpdate(t);
        } else {
            this.updateItem();
        }
    }

    private isContinueRoll: number = 0;
    //匀速运动
    private constantUpdate(t) {
        let currentY = this.contentNode.position.y - this.realySpeed;
        if (t === 1) {
            currentY = this.fastRollSwitch ? (-this.fastRollTriggerPoint * this.constantSpeed * 60) : this.targetContentPosY;
        }
        this.contentNode.position = new Vec3(this.contentNode.position.x, currentY, this.contentNode.position.z);
        this.updateItem()
        if(0.2 < t && !this.isContinueRoll){
            if(this.rollHook.continueRoll?.()){
                //有结果 停止
                this.isContinueRoll = 1;
            }else {
                //没有结果 继续滚动
                this.isContinueRoll = 2;
            }
        }
        if (t === 1) {
            if(this.isContinueRoll == 2){
                this.elapsed = 0;
                this.targetContentPosY = this.deltaTargetDistance + this.targetContentPosY;
                this.cycle += 1;
                this.isContinueRoll = 0;
            }else {
                if (!this.inertanceSwitch) {
                    if(!this.fastRollSwitch){
                        this.runFlag = NBSlotsScrollState.Motionless;
                        this.onComplete();
                    }else{
                        this.runFlag = NBSlotsScrollState.Fast;
                        this.elapsed = 0;
                        this.rollHook.fastRollStart?.()
                    }
                } else {
                    if(this.fastRollSwitch){
                        this.runFlag = NBSlotsScrollState.Fast;
                        this.elapsed = 0;
                        this.rollHook.fastRollStart?.()
                    }else {
                        this.runFlag = NBSlotsScrollState.Springback;
                        this.startBounce();
                    }
                }
            }
        }
    }

    //快速滚动
    private fastRollUpdate(t){
        let currentY = this.contentNode.position.y - this.realyFastRollSpeed;
        if (t === 1) {
            currentY = this.targetContentPosY;
        }
        this.contentNode.position = new Vec3(this.contentNode.position.x, currentY, this.contentNode.position.z);
        this.updateItem()
        if (t === 1) {
            if (!this.inertanceSwitch) {
                this.runFlag = NBSlotsScrollState.Motionless;
            } else {
                this.runFlag = NBSlotsScrollState.Springback;
                this.startBounce();
            }
        }
    }

    //开始回弹
    private startBounce() {
        const selftween = tween(this.contentNode)
            .by(this.inertanceDuration * 1 / 3, { position: new Vec3(0, -this.inertanceDistance, 0) }, {
                easing: 'quadOut'
            })
            .by(this.inertanceDuration * 2 / 3, { position: new Vec3(0, this.inertanceDistance, 0) }, {
                easing: 'quadOut', onComplete: () => {
                    this.onComplete();
                }
            })
            .start();
        this['selftween'] = selftween;
        this.rollHook.springbackStart?.()
    }

    //滚动完成
    private onComplete() {
        this.runFlag = NBSlotsScrollState.Motionless;
        this.targetContentPosY = 0;
        this.deltaTargetDistance = 0;
        this.deltaTargetIndex = 0;
        this.cycle = 0;
        this.isContinueRoll = 0;
        this.contentNode.position = new Vec3(this.contentNode.position.x,0,this.contentNode.position.z);
        //处理所有子节点位置
        this.childrens.forEach((v, index) => {
            const pos = new Vec3(v.position);
            pos.y = this.itemHeight / 2 + (index) * this.itemHeight;
            v.setPosition(pos)
        })
        const count = this.rows + 1;
        for (let i = 0; i < count; ++i) {
            const node = this.childrens[i];
            node['itemIndex'] = i 
        }
        this.elapsed = 0;
        if (this['selftween']) {
            this['selftween'].stop();
            this['selftween'] = null;
        }
        this.rollHook.rollEnd?.();
    }

    //更新子节点
    private updateItem() {
        const isUp = this.contentNode.position.y > this.lastContentPosY;
        const childs = this.childrens;
        const halfScrollView = this.node.getComponent(UITransform).height / 2;
        for (let i = 0; i < childs.length; ++i) {
            const item = childs[i];
            const viewPos = this._getPositionInView(item);
            if (isUp) {
                //如果item超过上边界 那么就移除
                if (viewPos.y - item.getComponent(UITransform).height / 2 > halfScrollView) {
                    this._removeItem(item);
                    childs.splice(i, 1);
                    i--;
                }
            } else {
                if (viewPos.y + item.getComponent(UITransform).height / 2 < -halfScrollView) {
                    this._removeItem(item);
                    childs.splice(i, 1);
                    i--;
                }
            }
        }

        if (isUp) {
            //创建下一个
            this._createNextItem();
        } else {
            //创建上一个
            this._createPreviousItem();
        }
        this.lastContentPosY = this.contentNode.position.y;
    }

    
    //开始滚动
    startScroll() {
        this.runFlag = NBSlotsScrollState.Constant;
        const targetIndex = this.calculateStopIndex()
        this.targetContentPosY = -targetIndex * this.itemHeight;
        if(!this.fastRollSwitch){
            this.deltaTargetDistance = this.targetContentPosY;
            this.deltaTargetIndex = targetIndex;
        }else {
            this.deltaTargetDistance = this.fastRollTriggerPoint * this.constantSpeed * 60;
            this.deltaTargetIndex =  Math.round(this.deltaTargetDistance / this.itemHeight);
        }
        this.isContinueRoll = 0;
        this.cycle = 0;
        //计算真实速度
        if(!this.fastRollSwitch){
            this.realySpeed = (targetIndex * this.itemHeight) / (this.duration * 60);
        }else {
            this.realySpeed = Math.round(this.fastRollTriggerPoint * this.constantSpeed * 60 / this.itemHeight) * this.itemHeight / (this.fastRollTriggerPoint * 60);
            this.realyFastRollSpeed = Math.round(this.fastRollDuration * this.fastRollSpeed * 60 / this.itemHeight) * this.itemHeight / (this.fastRollDuration * 60);
        }
        this.rollHook.rollStart?.();
    }

    //计算停止Index
    calculateStopIndex() {
        if(this.runFlag === NBSlotsScrollState.Motionless) {
            if (!this.fastRollSwitch) {
                const distance = this.duration * this.constantSpeed * 60;
                return Math.round(distance / this.itemHeight);
            } else {
                const distance = this.fastRollTriggerPoint * this.constantSpeed * 60  + this.fastRollDuration * this.fastRollSpeed * 60;
                return Math.round(distance / this.itemHeight);
            }
        }else {
            if (!this.fastRollSwitch) {
                const distance = this.duration * this.constantSpeed * 60;
                return this.deltaTargetIndex * this.cycle + Math.round(distance / this.itemHeight);
            } else {
                const distance = this.fastRollTriggerPoint * this.constantSpeed * 60  + this.fastRollDuration * this.fastRollSpeed * 60;
                return this.deltaTargetIndex * this.cycle + Math.round(distance / this.itemHeight);
            }
        }
   
    }

    reset() {
        if (this['selftween']) {
            this['selftween'].stop();
            this['selftween'] = null;
        }
        this.runFlag = NBSlotsScrollState.Motionless;
        this.targetContentPosY = 0;
        this.elapsed = 0;
        this.childrens.forEach((v) => {
            this._removeItem(v);
        })
        this.childrens.length = 0;
        this.contentNode.position = new Vec3(this.contentNode.position.x, 0, 1);
        this.numItems = 0;
        this._startCreateItems();
    }

    runState(): NBSlotsScrollState {
        return this.runFlag
    }

    onDestroy() {
        if (this['selftween']) {
            this['selftween'].stop();
            this['selftween'] = null;
        }
        this.childrens.forEach((v) => {
            v && v.destroy();
        })
        this.childrens.length = 0;
        this.nodePool.forEach((v) => {
            v && v.destroy();
        })
        this.nodePool.length = 0;
    }
}